/**
 * Copyright (c) 2009  Collaborative Development Group, C.S. Dept., University of Bari
 *
 * All rights reserved. This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License v1.0  which accompanies this distribution, and is 
 * available at http://www.eclipse.org/legal/epl-v10.html
 */
package it.uniba.di.cdg.penelope;

import static org.eclipse.core.runtime.ContributorFactoryOSGi.resolve;
import it.uniba.di.cdg.penelope.internal.Activator;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IExecutableExtensionFactory;
import org.ops4j.peaberry.eclipse.GuiceExtensionFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

import com.google.inject.Injector;

/**
 * Helper for supporting Guice/Peaberry dependency injection for Eclipse items (views, editors, handlers, ...).
 * Any extension point that supports {@link IExecutableExtension} and {@link IExecutableExtensionFactory} can use
 * this class.
 * <br/>
 * Note that the current implementation simply wraps {@link GuiceExtensionFactory}.
 * XXX Cache the Injector instances for faster retrieval? Do measurements first!
 */
public class PenelopeExtensionFactory implements IExecutableExtensionFactory, IExecutableExtension {

	public static final String PROPERTY_BUNDLE_NAME = "bundle.name";
	
	private IConfigurationElement configuration;
	private IContributor contributor;
	private String className;
	
	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.IExecutableExtensionFactory#create()
	 */
	@Override
	public Object create() throws CoreException {
		String bundleId = contributor.getName();
		
		Class<?> objectType = loadClass( className );
		Object instance = getInjector( bundleId ).getInstance( objectType ); 
		
	    if (instance instanceof IExecutableExtension) {
	        ((IExecutableExtension) instance).setInitializationData( configuration, null, null );
	      }
		return instance;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
	 */
	@Override
	public void setInitializationData( IConfigurationElement config, String propertyName, Object data )
			throws CoreException {
	    configuration = config;
	    // find contributor while still valid
	    contributor = config.getContributor();
	    // if there's no (string-based) adapter data then class must be under "id"
	    className = data instanceof String ? (String) data : config.getAttribute("id");
	}
	
	/**
	 * Searches for the associated Injector for this bundle.
	 * @param bundleId
	 * @return
	 */
	private Injector getInjector( String bundleId ) {
		final BundleContext bc = Activator.getDefault().getBundleContext();
		String filter = String.format( "(%s=%s)", PROPERTY_BUNDLE_NAME, bundleId );
		ServiceReference[] refs;
		try {
			refs = bc.getServiceReferences( Injector.class.getName(), filter );
		} catch (InvalidSyntaxException e) {
			throw new DependencyInjectionError( e );
		}
		if (refs.length != 1) {
			throw new DependencyInjectionError( 
					String.format( "Found %d registered Injectors for bundle %s. Only one is supported.", refs.length, bundleId ) );
		}
		Injector injector = (Injector) bc.getService( refs[0] );
		return injector;
	}

	private Class<?> loadClass( String className ) {
		try {
			return resolve(contributor).loadClass( className );
		} catch (ClassNotFoundException e) {
			throw new RuntimeException( e );
		}
	}
}
